sea's blog → Algebra, Lisp, and miscellaneous thoughts

Table of Contents

Why Lisp?

A lot of my friends ask me why in the hell I use Lisp. They can't comprehend it. Most of them seem to be turned off by the parenthesis, actually, and at least one of them I'm certain is just complaining to be difficult, and has never actually given lisp any more than a moment's worth of thought (terrible excuse for a logical person he is!)

I use Lisp for my initial prototypes and ideas, and in general for things that don't need strong correctness guarantees or extreme speed.

Speed

First, it's important to note that computers get much faster year over year. For personal projects (and those with a reasonable number of users (ie. any small business), a single computer is now most probably powerful enough to serve as your entire infrastructure without serious problems. We now use multiple systems for redundancy and failover, not for performance reasons (at small business/team scales).

Thus, interpreted languages are plenty fast enough. Don't forget that for some ungodly reason the industry seems to like using node.js for backend. Lisp interpreters (SBCL especially) actually compile down to machine code, you can disassemble it and it's usually very similar to C code, look:

(defun fibonacci-unoptimized (n)
        (if (member n '( 0 1)) 1
                (+
                        (fibonacci-unoptimized (- n 2))
                        (fibonacci-unoptimized (- n 1)))))
(print (disassemble #'fibonacci-unoptimized))
; disassembly for FIBONACCI-UNOPTIMIZED
; Size: 143 bytes. Origin: #x539BB1B4                         ; FIBONACCI-UNOPTIMIZED
; 1B4:       498B4510         MOV RAX, [R13+16]               ; thread.binding-stack-pointer
; 1B8:       488945F0         MOV [RBP-16], RAX
; 1BC:       48837DE800       CMP QWORD PTR [RBP-24], 0
; 1C1:       7508             JNE L2
; 1C3: L0:   BA02000000       MOV EDX, 2
; 1C8: L1:   C9               LEAVE
; 1C9:       F8               CLC
; 1CA:       C3               RET
; 1CB: L2:   48837DE802       CMP QWORD PTR [RBP-24], 2
; 1D0:       74F1             JEQ L0
; 1D2:       488B55E8         MOV RDX, [RBP-24]
; 1D6:       BF04000000       MOV EDI, 4
; 1DB:       FF142550060050   CALL [#x50000650]               ; #x52A00F80: GENERIC--
; 1E2:       4883EC10         SUB RSP, 16
; 1E6:       B902000000       MOV ECX, 2
; 1EB:       48892C24         MOV [RSP], RBP
; 1EF:       488BEC           MOV RBP, RSP
; 1F2:       B842CA3450       MOV EAX, #x5034CA42             ; #<FDEFN FIBONACCI-UNOPTIMIZED>
; 1F7:       FFD0             CALL #S(SB-X86-64-ASM::REG :ID 0)
; 1F9:       480F42E3         CMOVB RSP, RBX
; 1FD:       488955E0         MOV [RBP-32], RDX
; 201:       488B55E8         MOV RDX, [RBP-24]
; 205:       BF02000000       MOV EDI, 2
; 20A:       FF142550060050   CALL [#x50000650]               ; #x52A00F80: GENERIC--
; 211:       4883EC10         SUB RSP, 16
; 215:       B902000000       MOV ECX, 2
; 21A:       48892C24         MOV [RSP], RBP
; 21E:       488BEC           MOV RBP, RSP
; 221:       B842CA3450       MOV EAX, #x5034CA42             ; #<FDEFN FIBONACCI-UNOPTIMIZED>
; 226:       FFD0             CALL #S(SB-X86-64-ASM::REG :ID 0)
; 228:       480F42E3         CMOVB RSP, RBX
; 22C:       488BFA           MOV RDI, RDX
; 22F:       488B55E0         MOV RDX, [RBP-32]
; 233:       FF142548060050   CALL [#x50000648]               ; #x52A00F10: GENERIC-+
; 23A:       EB8C             JMP L1
; 23C:       CC10             INT3 16                         ; Invalid argument count trap
; 23E:       CC14             INT3 20                         ; UNDEFINED-FUN-ERROR
; 240:       00               BYTE #X00                       ; RAX(d)
; 241:       CC10             INT3 16                         ; Invalid argument count trap

NIL 

I also want to point out that, as anyone who's done leetcode (urgh! That deserves its own post!) knows, speed is actually more about the cleverness of your algorithm, not about your language (with the exception of special case languages like brainfuck or unlambda!)

Lisp is such an elegant, powerful, expressive language that you're able to use the more powerful and elegant abstract trickery to write the faster algorithms much more easily, so in general your code should usually be a lot faster than the fast-but-infantile code a C developer writes, unless that C developer has a month to optimie the code thoroughly. (C++ developers will need much less, since that's quite a powerful language now!)

Environment

Emacs and SLIME with paredit and so on. Note: My emacs config.

Enough has been said on the internet about Emacs and its integration with lisp. It is, simply, the single most powerful programming environment known to man (for lisp), and that remains undisputed.

Macros

People who code in languages without real macros will not be able to comprehend them, let alone begin to understand their glory. They simply don't have the mental framework needed. It would be like explaining to a geometer that there exists a connection between geometry and algebra, and if they could only study it a little, they'd get it.

It sufficies to say that all code is similar, there are only a few ideas that you reuse. Wherever there is redundancy in the code, and I really do mean wherever, you can write code to generate that code and eliminate it. That code-generating code is itself generatable, to the point where an obsessive lisper can simplify the codebase to a horrible mega-abstracted structure which probably is glorious and powerful enough to become its own branch of mathematics. (In fact, this is how new branches of mathematics are made! Abstracting repeatedly and distilling out common compoments until..)